#ifndef RB_TYPES_HPP
#define RB_TYPES_HPP

#include <assert.h>
#include <exception>
#include <algorithm>
#include <cstdlib>
#include <iterator>
#include <memory>
#include <map>
#include <vector>
#include <chrono>
#include <random>
//#include <unordered_map>

namespace rb_types {

/// status of function exection
enum class status {
    success,
    out_of_memory,
    invalid_arguments,
    iterrator_ends,
    runtime_error
};

class error : public std::exception {
public:
    status s;
    const char* message;

    error() = delete;

    /// Constructs an instance of an exception class.
    error(status st, const char* message):
        s(st), message(message) { }
    /// Returns the explanatory string.
    const char* what() const noexcept override {
        return message;
    }
};

/// block type
enum class block_type {
    long_bar = 0,
    T_shape,
    square_shape,
    L_left,
    L_right,
    Z_left,
    Z_right,
    undef
};


/// 2d-coordinate point : 0-index starting
class Point {
public:
    int x, y;
    Point(const int x, const int y):
        x(x), y(y) {}
};

/// block shapes and their rotations
static const std::map<block_type, std::vector<std::vector<Point> > > block_shapes {
    {{block_type::long_bar}, {                             // clockwise rotation
         {Point(0, 0), Point(1, 0), Point(2, 0), Point(3, 0)},    // 0   degrees
         {Point(0, 0), Point(0, -1), Point(0, -2), Point(0, -3)}, // 90  degrees
         {Point(0, 0), Point(-1, 0), Point(-2, 0), Point(-3, 0)}, // 180 degrees
         {Point(0, 0), Point(0, 1), Point(0, 2), Point(0, 3)}     // 270 degrees
     }},
    {{block_type::T_shape}, {
         {Point(0, 0), Point(1, 0), Point(2, 0), Point(1, 1)},
         {Point(0, 0), Point(0, -1), Point(0, -2), Point(1, -1)},
         {Point(0, 0), Point(-1, 0), Point(-2, 0), Point(-1, -1)},
         {Point(0, 0), Point(0, 1), Point(0, 2), Point(-1, 1)}
     }},
    {{block_type::square_shape}, {
         {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)},
         {Point(0, 0), Point(1, 0), Point(0, -1), Point(1, -1)},
         {Point(0, 0), Point(-1, 0), Point(0, -1), Point(-1, -1)},
         {Point(0, 0), Point(-1, 0), Point(0, 1), Point(-1, 1)}
     }},
    {{block_type::L_left}, {
         {Point(0, 0), Point(1, 0), Point(1, 1), Point(1, 2)},
         {Point(0, 0), Point(0, -1), Point(1, -1), Point(2, -1)},
         {Point(0, 0), Point(-1, 0), Point(-1, -1), Point(-1, -2)},
         {Point(0, 0), Point(0, 1), Point(-1, 1), Point(-2, 1)}
     }},
    {{block_type::L_right}, {
         {Point(0, 0), Point(1, 0), Point(0, 1), Point(0, 2)},
         {Point(0, 0), Point(1, 0), Point(2, 0), Point(0, -1)},
         {Point(0, 0), Point(-1, 0), Point(0, -1), Point(0, -2)},
         {Point(0, 0), Point(-1, 0), Point(-2, 0), Point(0, 1)}
     }},
    {{block_type::Z_left}, {
         {Point(0, 0), Point(1, 0), Point(1, 1), Point(2, 1)},
         {Point(0, 0), Point(0, -1), Point(1, -1), Point(1, -2)},
         {Point(0, 0), Point(-1, 0), Point(-1, -1), Point(-2, -1)},
         {Point(0, 0), Point(0, 1), Point(-1, 1), Point(-1, 2)}
     }},
    {{block_type::Z_left}, {
         {Point(0, 0), Point(1, 0), Point(1, 1), Point(2, 1)},
         {Point(0, 0), Point(0, -1), Point(1, -1), Point(1, -2)},
         {Point(0, 0), Point(-1, 0), Point(-1, -1), Point(-2, -1)},
         {Point(0, 0), Point(0, 1), Point(-1, 1), Point(-1, 2)}
     }},
    {{block_type::Z_right}, {
         {Point(1, 0), Point(2, 0), Point(0, 1), Point(1, 1)},
         {Point(1, 0), Point(1, -1), Point(0, -1), Point(0, -2)},
         {Point(-1, 0), Point(-2, 0), Point(0, -1), Point(-1, -1)},
         {Point(-1, 0), Point(-1, 1), Point(0, 1), Point(0, 2)}
     }}
};

static const std::map<block_type, std::vector<int> > block_shape_widths {
    {{block_type::long_bar},    {4, 1, 1, 1}},
    {{block_type::T_shape},     {3, 2, 1, 1}},
    {{block_type::square_shape}, {2, 2, 1, 1}},
    {{block_type::L_left},      {2, 3, 1, 1}},
    {{block_type::L_right},     {2, 3, 1, 1}},
    {{block_type::Z_left},      {3, 2, 1, 1}},
    {{block_type::Z_right},     {3, 2, 1, 1}}
};


// block generator type
enum class generator_type {
    undef = 0,
    random,
    rolling
};

/// Base class of block generator
class BlockGenerator {
public:
    BlockGenerator() = default;
    virtual ~BlockGenerator() = default;
    virtual generator_type getGeneratorType() const { return generator_type::undef; }
    virtual block_type fetch() = 0;
    virtual block_type next() const = 0;
    virtual unsigned int getNextNum() = 0;
    /// convert unsigned int to block_type
    static block_type num2bt(unsigned int r) {
        switch (r) {
        case 0:
            return block_type::long_bar;
        case 1:
            return block_type::T_shape;
        case 2:
            return block_type::square_shape;
        case 3:
            return block_type::L_left;
        case 4:
            return block_type::L_right;
        case 5:
            return block_type::Z_left;
        case 6:
            return block_type::Z_right;
        default:
            assert(!"random number out of range");
            return block_type::undef;
        }
    }
    // block_type oneBlock;
};

class RandomBlockGenerator : public BlockGenerator {
public:
    std::mt19937 generator;
    unsigned int rand_num;
    RandomBlockGenerator() {
        init();
    }
    void init() {
        unsigned newSeed = std::chrono::system_clock::now().time_since_epoch().count();
        generator.seed(newSeed);
        rand_num = getNextNum();
    }
    virtual generator_type getGeneratorType() const override {
        return generator_type::random;
    }
    /// make sure that func next() call after func fetch()
    virtual block_type fetch() override {
        int r = rand_num;
        rand_num = getNextNum();
        return num2bt(r);
    }
    virtual block_type next() const override {
        return num2bt(rand_num);
    }
    virtual unsigned int getNextNum() {
        return generator() % 7;
    }
};

class RollingBlockGenerator : public BlockGenerator {
public:
    unsigned int roll_num;
    RollingBlockGenerator() {
        init();
    }
    void init() {
        std::mt19937 generator(std::chrono::system_clock::now().time_since_epoch().count());
        roll_num = generator() % 7;
    }
    virtual generator_type getGeneratorType() const override {
        return generator_type::rolling;
    }
    /// make sure that func next() call after func fetch()
    virtual block_type fetch() override {
        int r = roll_num;
        roll_num = getNextNum();
        return num2bt(r);
    }
    virtual block_type next() const override {
        return num2bt(roll_num);
    }
    virtual unsigned int getNextNum() {
        return (roll_num + 1) % 7;
    }
};


} // namespace rb_types

#endif // RB_TYPES_HPP
